Skip to content

Conversation

@3xp0rt
Copy link

@3xp0rt 3xp0rt commented Aug 31, 2025

Refactoring Pull Request

Refactoring Scope

The Tab class's execute_script method in pydoll/browser/tab.py, related tests and documentation.

Related Issue(s)

Addresses user feedback from GitHub Discussion #108 regarding async script execution capabilities

Description

Refactor the execute_script method by separating concerns to improve the API design. The new implementation should support asynchronous execution using await_promise=True and return_by_value=True, provide additional options for flexible usage in code. The original method handled both general script execution and element-specific execution, which has been split into two focused methods:

  • execute_script: Enhanced with comprehensive runtime options for general script execution
  • execute_element_script: New method specifically for executing scripts with element context

Motivation

Support for asynchronous code.

Before / After

Before

    @overload
    async def execute_script(self, script: str) -> EvaluateResponse: ...

    @overload
    async def execute_script(self, script: str, element: WebElement) -> CallFunctionOnResponse: ...

    async def execute_script(
        self, script: str, element: Optional[WebElement] = None
    ) -> Union[EvaluateResponse, CallFunctionOnResponse]:

After

    async def execute_script(
        self,
        script: str,
        *,
        object_group: Optional[str] = None,
        include_command_line_api: Optional[bool] = None,
        silent: Optional[bool] = None,
        context_id: Optional[int] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        throw_on_side_effect: Optional[bool] = None,
        timeout: Optional[float] = None,
        disable_breaks: Optional[bool] = None,
        repl_mode: Optional[bool] = None,
        allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> EvaluateResponse:
    
    async def execute_element_script(
        self,
        script: str,
        element: WebElement,
        *,
        arguments: Optional[list[CallArgument]] = None,
        silent: Optional[bool] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        execution_context_id: Optional[int] = None,
        object_group: Optional[str] = None,
        throw_on_side_effect: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> CallFunctionOnResponse:

Performance Impact

  • Performance improved
  • Performance potentially decreased
  • No significant performance change
  • Performance impact unknown

Technical Debt

This refactoring addresses several technical debt issues:

  • Mixed Responsibilities: The original method handled two different use cases
  • Complex Method Signatures: Overloaded methods with optional parameters made the API confusing
  • Limited Extensibility: The previous design lacked support for passing additional options and asynchronous execution, restricting flexibility

API Changes

  • No changes to public API
  • Public API changed, but backward compatible
  • Breaking changes to public API

Testing Strategy

  • Updated existing tests to use the new method signatures
  • Verified that all existing functionality is preserved through the new API

Testing Checklist

  • Existing tests updated
  • New tests added for previously uncovered cases
  • All tests pass
  • Code coverage maintained or improved

Risks and Mitigations

  • Breaking change for users who relied on the old execute_script(script, element) signature

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have performed a thorough self-review of the refactored code
  • I have commented my code, particularly in complex areas
  • I have updated documentation if needed
  • I have run poetry run task lint and fixed any issues
  • I have run poetry run task test and all tests pass
  • My commits follow the conventional commits style

@codecov
Copy link

codecov bot commented Aug 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@3xp0rt 3xp0rt changed the title refactor(tab): separate execute_script concerns and enhance with comp… refactor(tab): separate execute_script concerns Aug 31, 2025
@3xp0rt
Copy link
Author

3xp0rt commented Sep 3, 2025

Last commit refactors JavaScript execution by moving execute_element_script functionality from the Tab class to the WebElement class for better encapsulation.

@thalissonvs
Copy link
Member

I think this is too much. We can keep something with a minimal useful API, and for advanced users, they can execute the scripts directly.

For instance, something like this would be better:

 @overload
 async def execute_script(self, script: str, await_promise: bool = True, return_by_value: bool = True) -> EvaluateResponse: ...
 @overload
 async def execute_script(self, script: str, element: WebElement, await_promise: bool = True, return_by_value: bool = True) -> CallFunctionOnResponse: ...

@3xp0rt
Copy link
Author

3xp0rt commented Oct 15, 2025

I think this is too much. We can keep something with a minimal useful API, and for advanced users, they can execute the scripts directly.

For instance, something like this would be better:

 @overload
 async def execute_script(self, script: str, await_promise: bool = True, return_by_value: bool = True) -> EvaluateResponse: ...
 @overload
 async def execute_script(self, script: str, element: WebElement, await_promise: bool = True, return_by_value: bool = True) -> CallFunctionOnResponse: ...

Oh, I completely forgot about this PR due to the delayed response, and you probably closed it because of inactivity. If that’s the case, it would’ve been better to just ping me instead of closing it. I’ve just submitted the latest commit with the requested changes, so please consider reopening this PR. Also, let me explain the situation a little better.

The current code from the PR uses explicit methods. Tab.execute_script is clearly intended for page-level JavaScript execution, while WebElement.execute_script is meant for element-specific operations. Both rely on different runtime functions and return different data types, so combining them would blur their distinct use cases and create confusion. Since the WebElement class already implements this function, duplicating it in WebElement isn’t necessary.

  1. Tab.execute_script (in pydoll/browser/tab.py):
async def execute_script(
   self,
   script: str,
   *,
   return_by_value: Optional[bool] = None,
   await_promise: Optional[bool] = None,
) -> EvaluateResponse:
  1. WebElement.execute_script (in pydoll/elements/web_element.py):
async def execute_script(
    self,
    script: str,
    *,
    return_by_value: Optional[bool] = None,
    await_promise: Optional[bool] = None,
) -> CallFunctionOnResponse:

@3xp0rt
Copy link
Author

3xp0rt commented Oct 15, 2025

Honestly, it’d be great to keep the other arguments, as having the flexibility to tweak these parameters could be useful in some situations.

@thalissonvs thalissonvs reopened this Oct 17, 2025
@thalissonvs
Copy link
Member

thalissonvs commented Oct 17, 2025

Hello @3xp0rt, thanks for the feedback. We have to keep the API consistent, otherwise it'll be a breaking change. I understand your point of view, and that makes sense. My idea would be: if the user pass an WebElement to the Tab.execute_script method, we can show a warning message informing that this is deprecated, and to use WebElement.execute_script instead;

For now, we can keep all the arguments, but we should preserve in just one method for clarity. Lets keep just execute_script method, and use overload for correct typing inference:

    
    @overload    # this is the signature without passing an WebElement as argument;
    async def execute_script(
        self,
        script: str,
        *,
        object_group: Optional[str] = None,
        include_command_line_api: Optional[bool] = None,
        silent: Optional[bool] = None,
        context_id: Optional[int] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        throw_on_side_effect: Optional[bool] = None,
        timeout: Optional[float] = None,
        disable_breaks: Optional[bool] = None,
        repl_mode: Optional[bool] = None,
        allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> EvaluateResponse:
    
    @overload
    async def execute_element(    # this is the signature passing an WebElement as arg
        self,
        script: str,
        element: WebElement,
        *,
        arguments: Optional[list[CallArgument]] = None,
        silent: Optional[bool] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        execution_context_id: Optional[int] = None,
        object_group: Optional[str] = None,
        throw_on_side_effect: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> CallFunctionOnResponse:


    async def execute_element(    # this is the actual implementation
        self,
        script: str,
        element: Optional[WebElement] = None,
        *,
        arguments: Optional[list[CallArgument]] = None,
        silent: Optional[bool] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        execution_context_id: Optional[int] = None,
        object_group: Optional[str] = None,
        throw_on_side_effect: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> Union[EvaluateResponse, CallFunctionOnResponse]:

What do you think? Just make sure to document every param properly

Copilot AI review requested due to automatic review settings October 28, 2025 00:27
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the Tab.execute_script() method to separate concerns between general script execution and element-specific execution. The refactoring deprecates passing a WebElement to Tab.execute_script(), directing users to call WebElement.execute_script() directly instead. Both methods now expose comprehensive Chrome DevTools Protocol parameters for Runtime.evaluate and Runtime.callFunctionOn, enabling support for asynchronous execution and other advanced options.

Key Changes:

  • Enhanced Tab.execute_script() with full CDP Runtime.evaluate parameters (including await_promise, return_by_value, etc.)
  • Enhanced WebElement.execute_script() with full CDP Runtime.callFunctionOn parameters
  • Deprecated element parameter in Tab.execute_script() with a deprecation warning
  • Removed InvalidScriptWithElement exception and related validation logic
  • Added Chromium binary paths for Linux systems

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pydoll/browser/tab.py Refactored execute_script to expose all CDP parameters and deprecate element parameter
pydoll/elements/web_element.py Enhanced execute_script with comprehensive CDP parameters
pydoll/exceptions.py Removed InvalidScriptWithElement exception class
tests/test_browser/test_browser_tab.py Updated tests for new API and added deprecation warning tests
tests/test_web_element.py Added comprehensive tests for WebElement.execute_script with various scenarios
pydoll/browser/chromium/chrome.py Added chromium binary paths for Linux
tests/test_browser/test_browser_chrome.py Updated test data for new Linux binary paths
tests/test_browser/test_browser_base.py Updated test data for new Linux binary paths

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@3xp0rt
Copy link
Author

3xp0rt commented Oct 28, 2025

Hello @3xp0rt, thanks for the feedback. We have to keep the API consistent, otherwise it'll be a breaking change. I understand your point of view, and that makes sense. My idea would be: if the user pass an WebElement to the Tab.execute_script method, we can show a warning message informing that this is deprecated, and to use WebElement.execute_script instead;

For now, we can keep all the arguments, but we should preserve in just one method for clarity. Lets keep just execute_script method, and use overload for correct typing inference:

    
    @overload    # this is the signature without passing an WebElement as argument;
    async def execute_script(
        self,
        script: str,
        *,
        object_group: Optional[str] = None,
        include_command_line_api: Optional[bool] = None,
        silent: Optional[bool] = None,
        context_id: Optional[int] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        throw_on_side_effect: Optional[bool] = None,
        timeout: Optional[float] = None,
        disable_breaks: Optional[bool] = None,
        repl_mode: Optional[bool] = None,
        allow_unsafe_eval_blocked_by_csp: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> EvaluateResponse:
    
    @overload
    async def execute_element(    # this is the signature passing an WebElement as arg
        self,
        script: str,
        element: WebElement,
        *,
        arguments: Optional[list[CallArgument]] = None,
        silent: Optional[bool] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        execution_context_id: Optional[int] = None,
        object_group: Optional[str] = None,
        throw_on_side_effect: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> CallFunctionOnResponse:


    async def execute_element(    # this is the actual implementation
        self,
        script: str,
        element: Optional[WebElement] = None,
        *,
        arguments: Optional[list[CallArgument]] = None,
        silent: Optional[bool] = None,
        return_by_value: Optional[bool] = None,
        generate_preview: Optional[bool] = None,
        user_gesture: Optional[bool] = None,
        await_promise: Optional[bool] = None,
        execution_context_id: Optional[int] = None,
        object_group: Optional[str] = None,
        throw_on_side_effect: Optional[bool] = None,
        unique_context_id: Optional[str] = None,
        serialization_options: Optional[SerializationOptions] = None,
    ) -> Union[EvaluateResponse, CallFunctionOnResponse]:

What do you think? Just make sure to document every param properly

Hello! Your idea is great, which is why I tried to implement it in the recent commits. I also added Chromium paths for Linux to fix a workflow issue I was experiencing. Please review the changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants